﻿using System;
using System.Collections.Generic;

using DDDSample.Common.Validate;
using DDDSample.Domain.Model.Cargos;
using DDDSample.Domain.Model.Carrier;
using DDDSample.Domain.Model.Locations;

namespace DDDSample.Domain.Model.Handling
{
	/// <summary>
	/// A HandlingEvent is used to register the event when, for instance,
	/// a cargo is unloaded from a carrier at a some loacation at a given time.
	/// <para />
	/// The HandlingEvent's are sent from different Incident Logging Applications
	/// some time after the event occured and contain information about the
	/// {@link se.citerus.dddsample.domain.model.cargo.TrackingId}, {@link se.citerus.dddsample.domain.model.location.Location}, timestamp of the completion of the event,
	/// and possibly, if applicable a {@link se.citerus.dddsample.domain.model.carrier.CarrierMovement}.
	/// <p/>
	/// This class is the only member, and consequently the root, of the HandlingEvent aggregate. 
	/// <p/>
	/// HandlingEvent's could contain information about a {@link CarrierMovement} and if so,
	/// the event type must be either {@link Type#LOAD} or {@link Type#UNLOAD}.
	/// <p/>
	/// All other events must be of {@link Type#RECEIVE}, {@link Type#CLAIM} or {@link Type#CUSTOMS}.
	/// </summary>
	public sealed class HandlingEvent : DomainEvent<HandlingEvent>
	{
		private EventType type;
		private CarrierMovement carrierMovement;
		private Location location;
		private DateTime completionTime;
		private DateTime registrationTime;
		private Cargo cargo;

		/// <summary>
		/// Comparer used to be able to sort HandlingEvents according to their completion time
		/// </summary>
		public static readonly IComparer<HandlingEvent> BY_COMPLETION_TIME_COMPARER = new __Comparer();

		/// <summary>
		/// 
		/// </summary>
		/// <param name="cargo">cargo</param>
		/// <param name="completionTime">completion time, the reported time that the event actually happened (e.g. the receive took place).</param>
		/// <param name="registrationTime">registration time, the time the message is received</param>
		/// <param name="type">type of event. Legal values are LOAD and UNLOAD</param>
		/// <param name="location">where the event took place</param>
		/// <param name="carrierMovement">carrier movement.</param>
		public HandlingEvent(Cargo cargo, DateTime completionTime, DateTime registrationTime, EventType type,
			Location location, CarrierMovement carrierMovement)
		{
			Validate.NotNull(cargo);
			Validate.NotNull(location);

			this.cargo = cargo;
			this.completionTime = completionTime;
			this.registrationTime = registrationTime;
			this.type = type;
			this.location = location;
			this.carrierMovement = carrierMovement;

			ValidateType();
		}

		public EventType Type
		{
			get { return this.type; }
		}

		public CarrierMovement CarrierMovement
		{
			get { return this.carrierMovement; }
		}

		public Location Location
		{
			get { return this.location; }
		}

		public DateTime CompletionTime
		{
			get { return this.completionTime; }
		}

		public DateTime RegistrationTime
		{
			get { return this.registrationTime; }
		}

		public Cargo Cargo
		{
			get { return this.cargo; }
		}

		public override bool Equals(object obj)
		{
			if (this == obj) return true;
			if (obj == null || GetType() != obj.GetType()) return false;

			HandlingEvent other = (HandlingEvent)obj;
			return SameEventAs(other);
		}

		public override bool SameEventAs(HandlingEvent other)
		{
			return other != null &&
				this.cargo == other.cargo &&
				this.carrierMovement == other.carrierMovement &&
				this.completionTime == other.completionTime &&
				this.location == other.location &&
				this.type == other.type;
		}

		public override int GetHashCode()
		{
			int hashCode = 37 + this.cargo.GetHashCode();
			hashCode += hashCode * 17 + this.completionTime.GetHashCode();
			hashCode += hashCode * 17 + this.location.GetHashCode();
			hashCode += hashCode * 17 + this.type.GetHashCode();
			hashCode += hashCode * 17 + (this.carrierMovement != null ? this.carrierMovement.GetHashCode() : 0);
			return hashCode;
		}

		/// <summary>
		/// Validate that the event type is compatible with the carrier movement value.
		/// <p/>
		/// Only certain types of events may be associated with a carrier movement.
		/// </summary>
		private void ValidateType()
		{
			if (type.RequiresCarrierMovement() && carrierMovement == null)
			{
				throw new ArgumentException("Carrier movement is required for event type " + type);
			}
			if (type.ProhibitsCarrierMovement() && carrierMovement != null)
			{
				throw new ArgumentException("Carrier movement is not allowed with event type " + type);
			}
		}

		/// <summary>
		/// Handling event type. Either requires or prohibits a carrier movement
		/// association, it's never optional.
		/// </summary>
		public enum EventType
		{
			Load,
			Unload,
			Receive,
			Claim,
			Customs
		}

		private class __Comparer : Comparer<HandlingEvent>
		{
			public override int Compare(HandlingEvent x, HandlingEvent y)
			{
				return x.completionTime.CompareTo(y.completionTime);
			}
		}

		private HandlingEvent()
		{
			// Needed by Hibernate
		}

		// Auto-generated surrogate key
		private long id;
	}
}
